import Events;
import Screen;
import FPS;

import loader.Cfg;

import std.stdio:writefln;
import std.c.math:sqrt;
import std.c.stdlib:malloc;

import derelict.opengl.gl;
import derelict.opengl.glu;
import derelict.opengl.extension.ext.framebuffer_object;

import cgGL;
import std.string;

const GLenum GL_RGBA32F_ARB	= 0x8814;


// actual data
float* dataY;


int texSize;
// texture identifiers
GLuint yTexID[2];

int writeTex = 0;
int readTex = 1;

// FBO identifier
GLuint fb;

CGcontext	myCgContext;
CGprofile   myCgFragmentProfile;
CGprogram   myCgFragmentProgram;
CGparameter yParam;

char[] myFragmentProgramFileName = "cg\\C2E1v_green.cg";
char[] myFragmentProgramName = "pi";

int numIterations = 1;

void init(){
    DerelictGL.load();
    DerelictGLU.load();
}

void cleanup(){
	writefln("STFU");
}

void checkForCgError(char[] situation){
	CGerror error;

	char* string = cgGetLastErrorString(&error);
	if(error != CGerror.CG_NO_ERROR) {
	    writefln("%s: %s", situation, toString(string) );
		if(error == CGerror.CG_COMPILER_ERROR)
			writefln(toString(cgGetLastListing(myCgContext)));
	}

}

void initFBO() {
	writefln("GL_EXT_framebuffer_object: ",EXTFramebufferObject.load("GL_EXT_framebuffer_object"));
	// create FBO (off-screen framebuffer)
	assert(glGenFramebuffersEXT !is null);
    glGenFramebuffersEXT(1, &fb); 
// bind offscreen framebuffer (that is, skip the window-specific render target)
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
    // viewport for 1:1 pixel=texture mapping
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluOrtho2D(0.0, texSize, 0.0, texSize);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, texSize, texSize);
}

void setupTexture (GLuint texID) {
	// make active and bind
	glBindTexture(GL_TEXTURE_2D,texID);
	// turn off filtering and wrap modes
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	// define texture with floating point format
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, texSize, texSize, 0, GL_RGBA, GL_FLOAT, null);
}

void transferToTexture (float* data, GLuint texID) {
	// version (a): HW-accelerated on NVIDIA 
	glBindTexture(GL_TEXTURE_2D, texID);
	glTexSubImage2D(GL_TEXTURE_2D,0,0,0,texSize,texSize,GL_RGBA,GL_FLOAT,data);
}

void transferFromTexture(float* data) {
	// version (a): texture is attached
	// recommended on both NVIDIA and ATI
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,data);
}


void createTextures () {
    // create textures 
    // y gets two textures, alternatingly read-only and write-only, 
    // x is just read-only
    glGenTextures (2, yTexID);
    // set up textures
    setupTexture (yTexID[readTex]);
	transferToTexture(dataY,yTexID[readTex]);
    setupTexture (yTexID[writeTex]);
	transferToTexture(dataY,yTexID[writeTex]);
	// set texenv mode from modulate (the default) to replace)
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

void initMyCgProgram(){
	myCgContext = cgCreateContext();
	checkForCgError("creating context");

	myCgFragmentProfile = cgGLGetLatestProfile(CGGLenum.CG_GL_FRAGMENT);
	cgGLSetOptimalOptions(myCgFragmentProfile);
	checkForCgError("selecting vertex profile");
	writefln(cast(int)myCgFragmentProfile);

	myCgFragmentProgram = 
		cgCreateProgramFromFile(
				myCgContext,
				CGenum.CG_SOURCE,
				toStringz(myFragmentProgramFileName),
				myCgFragmentProfile,
				toStringz(myFragmentProgramName),
				null);
	checkForCgError("creating vertex program from file");
	cgGLLoadProgram(myCgFragmentProgram);
	checkForCgError("loading vertex program");
	writefln("Loaded.");
	yParam = cgGetNamedParameter(myCgFragmentProgram, toStringz("textureY"));
	checkForCgError("cgGetNamedParameter");
}

void performComputation() {
	//	glEnable(textureParameters.texTarget);
	// attach two textures to FBO
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, yTexID[writeTex], 0);//write
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, yTexID[readTex], 0);//read

	// enable fragment profile
	cgGLEnableProfile(myCgFragmentProfile);
	checkForCgError("cgGLEnableProfile");	
	// bind saxpy program
	cgGLBindProgram(myCgFragmentProgram);
	checkForCgError("cgGLBindProgram");	
	// enable texture x (read-only, not changed during the iteration)
	// Calling glFinish() is only neccessary to get accurate timings,
	// and we need a high number of iterations to avoid timing noise.
	glFinish();

    for (int i=0; i<numIterations; i++) {
		// set render destination
		glDrawBuffer (GL_COLOR_ATTACHMENT0_EXT);//write
		// enable texture y_old (read-only)
		cgGLSetTextureParameter(yParam, yTexID[readTex]);
		checkForCgError("cgGLSetTextureParameter");
		cgGLEnableTextureParameter(yParam);
		checkForCgError("cgGLEnableTextureParameter");
		// and render multitextured viewport-sized quad
		// depending on the texture target, switch between 
		// normalised ([0,1]^2) and unnormalised ([0,w]x[0,h])
		// texture coordinates
		
		// make quad filled to hit every pixel/texel 
		// (should be default but we never know)
		glPolygonMode(GL_FRONT,GL_FILL);
		// and render the quad
			// render with normalized texcoords
			glBegin(GL_QUADS);
				glTexCoord2f(0.0, 0.0); 
				glVertex2f(0.0, 0.0);
				glTexCoord2f(1.0, 0.0); 
				glVertex2f(texSize, 0.0);
				glTexCoord2f(1.0, 1.0); 
				glVertex2f(texSize, texSize);
				glTexCoord2f(0.0, 1.0); 
				glVertex2f(0.0, texSize);
			glEnd();

		// swap role of the two textures (read-only source becomes 
		// write-only target and the other way round):
		swap();
    }

	// done, just do some checks if everything went smoothly.
    checkFramebufferStatus();
}

void swap() {
    if (writeTex == 0) {
		writeTex = 1;
		readTex = 0;
    } else {
		writeTex = 0;
		readTex = 1;
    }
}

bool checkFramebufferStatus() {
    GLenum status;
    status = cast(GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status) {
        case GL_FRAMEBUFFER_COMPLETE_EXT:
            return true;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			printf("Framebuffer incomplete, incomplete attachment\n");
            return false;
        case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			printf("Unsupported framebuffer format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			printf("Framebuffer incomplete, missing attachment\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			printf("Framebuffer incomplete, attached images must have same dimensions\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			printf("Framebuffer incomplete, attached images must have same format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			printf("Framebuffer incomplete, missing draw buffer\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			printf("Framebuffer incomplete, missing read buffer\n");
            return false;
    }
	return false;
}



void main(){
	init();
	Events events = new Events();
	screen();

	int N = 4;
	texSize = cast(int)sqrt(cast(float)N/4.0);

	dataY = cast(float*)malloc(N * float.sizeof);
    // and fill with some arbitrary values
	int sqrN = cast(int)sqrt(N);
	for (int i=0; i<N; i++) {
		dataY[i] = cast(float)(i/sqrN);
//		writefln( dataX[i], " - ", dataY[i]);
	}

	initFBO();

	createTextures();
	
	initCg();
	initCgGl();

	initMyCgProgram();
	writefln("Computation...");
	
	performComputation();

    float* data = cast(float*)malloc(N*float.sizeof);
    transferFromTexture(data);
	checkForCgError("transferFromTexture");

    for (int i=0; i<N; i++){
		writefln(data[i]);
	}

    glDeleteFramebuffersEXT(1,&fb);
    glDeleteTextures(2,yTexID);


/+	FPS fps = new FPS(2000);

	scope(exit){
		cleanup();
	}

	while(true){
		events.check();
//		world().render();
		fps.nextFrame();
		fps.update();
	}
+/
}
